import { REGISTRY } from "../system-core";
import { errMsg } from "../err-msg";
import { SystemJS } from "../s";
import { Load } from "../Load";

let toStringTag = typeof Symbol !== "undefined" && Symbol.toStringTag;

export const get = function (this: SystemJS, id: string) {
    let load = this[REGISTRY][id];
    if (load && load.e === null && !load.E) {
        if (load.er) return null;
        return load.n;
    }
};

export const set = function (this: SystemJS, id: string, module: Load["n"]) {
    if (!process.env.SYSTEM_PRODUCTION) {
        try {
            // No page-relative URLs allowed
            new URL(id);
        } catch (err) {
            console.warn(
                Error(
                    errMsg(
                        "W3",
                        '"' +
                            id +
                            '" is not a valid URL to set in the module registry'
                    )
                )
            );
        }
    }
    let ns;
    /* @ts-ignore */
    if (toStringTag && module[toStringTag] === "Module") {
        ns = module;
    } else {
        ns = Object.assign(Object.create(null), module);
        if (toStringTag)
            Object.defineProperty(ns, toStringTag, { value: "Module" });
    }

    let done = Promise.resolve(ns);

    let load =
        this[REGISTRY][id] ||
        /* @ts-ignore */
        (this[REGISTRY][id] = {
            id: id,
            i: [],
            h: false,
            d: [],
            e: null,
            er: undefined,
            E: undefined,
        });

    if (load.e || load.E) return false;

    Object.assign(load, {
        n: ns,
        I: undefined,
        L: undefined,
        C: done,
    });
    return ns;
};

export const has = function (this: SystemJS, id: string) {
    let load = this[REGISTRY][id];
    return !!load;
};

// Delete function provided for hot-reloading use cases
export function _delete(this: SystemJS, id: string) {
    let registry = this[REGISTRY];
    let load = registry[id];
    // in future we can support load.E case by failing load first
    // but that will require TLA callbacks to be implemented
    if (!load || (load.p && load.p.e !== null) || load.E) return false;

    let importerSetters: Load["i"] | null = load.i;
    // remove from importerSetters
    // (release for gc)
    if (load.d)
        load.d.forEach(function (depLoad) {
            /* @ts-ignore */
            let importerIndex = depLoad.i.indexOf(load);
            if (importerIndex !== -1) depLoad.i.splice(importerIndex, 1);
        });
    delete registry[id];
    return function () {
        let load = registry[id];
        if (!load || !importerSetters || load.e !== null || load.E)
            return false;
        // add back the old setters
        importerSetters.forEach(function (setter) {
            load.i.push(setter);
            /* @ts-ignore */
            setter(load.n);
        });
        importerSetters = null;
    };
}

let iterator = Symbol.iterator;

export const entries = function (this: SystemJS) {
    let loader = this,
        keys = Object.keys(loader[REGISTRY]);
    let index = 0;
    let ns: ReturnType<typeof get> | undefined;
    let key: string | undefined;
    let result = {
        next: function () {
            while (
                (key = keys[index++]) !== undefined &&
                (ns = ((loader as any).get as typeof get)(key)) === undefined
            );
            return {
                done: key === undefined,
                value: key !== undefined && [key, ns],
            };
        },
    };
    /* @ts-ignore */
    result[iterator] = function () {
        return this;
    };

    return result;
};
